using Microsoft.EntityFrameworkCore;
using NotifyCenter.EntityFrameworkCore;
using NotifyCenter.Projects;
using NotifyCenter.Projects.Dtos;

public class ProjectService : IProjectService
{
    private const int DefaultMaxRetryTimes = 3;
    private const int MaxSupportedRetryTimes = 10;

    private DbContext DbContext { get; }
    private DbSet<Project> Projects { get; }
    private DbSet<NotificationRule> Notifications { get; }

    public ProjectService(NotifyCenterDbContext dbContext)
    {
        DbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
        Projects = dbContext.Set<Project>();
        Notifications = dbContext.Set<NotificationRule>();
    }

    public async Task<List<NotificationRule>> AddNotificationsAsync(long projectId, NotificationRuleRequest notification, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(notification);
        ValidateNotificationRule(notification);

        var project = await GetProjectOrThrowAsync(projectId, cancellationToken);

        var now = DateTime.UtcNow;
        var entity = new NotificationRule
        {
            Name = notification.Name.Trim(),
            ProjectId = project.Id,
            Type = notification.Type,
            ConfigurationJson = notification.ConfigurationJson,
            CreateTime = now,
            UpdateTime = now,
            Description = notification.Description?.Trim() ?? string.Empty,
        };
        ApplyNotificationRuleValues(entity, notification);

        await Notifications.AddAsync(entity, cancellationToken);
        await DbContext.SaveChangesAsync(cancellationToken);

        return await GetAllNotificationsAsync(projectId, cancellationToken);
    }

    public async Task<ProjectDto> CreateAsync(ProjectCreateDto input, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(input);
        ValidateProjectName(input.Name);

        var now = DateTime.UtcNow;
        var entity = new Project
        {
            Name = input.Name.Trim(),
            Description = input.Description?.Trim() ?? string.Empty,
            Enabled = true,
            CreateTime = now,
            UpdateTime = now
        };

        await Projects.AddAsync(entity, cancellationToken);
        await DbContext.SaveChangesAsync(cancellationToken);

        return ToDto(entity);
    }

    public async Task<ProjectDto> EnableAsync(long projectId, bool enable, CancellationToken cancellationToken = default)
    {
        var project = await GetProjectOrThrowAsync(projectId, cancellationToken);

        project.Enabled = enable;
        project.UpdateTime = DateTime.UtcNow;
        await DbContext.SaveChangesAsync(cancellationToken);

        return ToDto(project);
    }

    public async Task<List<NotificationRule>> GetAllNotificationsAsync(long projectId, CancellationToken cancellationToken = default)
    {
        _ = await GetProjectOrThrowAsync(projectId, cancellationToken);

        return await Notifications
            .AsNoTracking()
            .Where(notification => notification.ProjectId == projectId)
            .ToListAsync(cancellationToken);
    }

    public async Task<ProjectPageResponse> GetListAsync(ProjectPageRequest input, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(input);

        var query = Projects.AsNoTracking();

        if (!string.IsNullOrWhiteSpace(input.Name))
        {
            var name = input.Name.Trim();
            query = query.Where(project => EF.Functions.Like(project.Name, $"%{name}%"));
        }

        if (!string.IsNullOrWhiteSpace(input.Description))
        {
            var description = input.Description.Trim();
            query = query.Where(project => EF.Functions.Like(project.Description, $"%{description}%"));
        }

        if (input.Enabled.HasValue)
        {
            query = query.Where(project => project.Enabled == input.Enabled);
        }

        query = query.OrderByDescending(project => project.CreateTime);

        var total = await query.CountAsync(cancellationToken);

        var skip = Math.Max(0, input.SkipCount);
        var take = input.MaxResultCount > 0 ? input.MaxResultCount : 10;

        var items = await query
            .Skip(skip)
            .Take(take)
            .Select(project => new ProjectDto
            {
                Id = project.Id,
                Name = project.Name,
                Description = project.Description,
                Enabled = project.Enabled
            })
            .ToListAsync(cancellationToken);

        return new ProjectPageResponse
        {
            TotalCount = total,
            Items = items
        };
    }

    public async Task<ProjectDto> UpdateAsync(long porjectId, ProjectUpdateDto input, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(input);
        ValidateProjectName(input.Name);

        var project = await GetProjectOrThrowAsync(porjectId, cancellationToken);

        project.Name = input.Name.Trim();
        project.Description = input.Description?.Trim() ?? string.Empty;
        project.UpdateTime = DateTime.UtcNow;

        await DbContext.SaveChangesAsync(cancellationToken);

        return ToDto(project);
    }

    public async Task<List<NotificationRule>> UpdateNotificationsAsync(long projectId, long notificationId, NotificationRuleRequest notification, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(notification);
        ValidateNotificationRule(notification);

        _ = await GetProjectOrThrowAsync(projectId, cancellationToken);

        var entity = await Notifications.FirstOrDefaultAsync(item => item.ProjectId == projectId && item.Id == notificationId, cancellationToken)
            ?? throw new InvalidOperationException($"Notification {notificationId} not found in project {projectId}.");

        var now = DateTime.UtcNow;

        entity.Name = notification.Name.Trim();
        entity.Type = notification.Type;
        entity.ConfigurationJson = notification.ConfigurationJson;
        entity.Description = notification.Description?.Trim() ?? string.Empty;
        ApplyNotificationRuleValues(entity, notification);
        entity.UpdateTime = now;

        await DbContext.SaveChangesAsync(cancellationToken);

        return await GetAllNotificationsAsync(projectId, cancellationToken);
    }

    private async Task<Project> GetProjectOrThrowAsync(long projectId, CancellationToken cancellationToken)
    {
        if (projectId <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(projectId));
        }

        var project = await Projects.FirstOrDefaultAsync(item => item.Id == projectId, cancellationToken)
            ?? throw new InvalidOperationException($"Project {projectId} not found.");

        return project;
    }

    private static ProjectDto ToDto(Project project)
    {
        return new ProjectDto
        {
            Id = project.Id,
            Name = project.Name,
            Description = project.Description,
            Enabled = project.Enabled
        };
    }

    private static void ValidateProjectName(string? name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            throw new ArgumentException("Project name cannot be empty.", nameof(name));
        }
    }

    private static void ValidateNotificationRule(NotificationRuleRequest rule)
    {
        if (string.IsNullOrWhiteSpace(rule.Name))
        {
            throw new ArgumentException("Notification rule name cannot be empty.", nameof(rule.Name));
        }

        if (string.IsNullOrWhiteSpace(rule.ConfigurationJson))
        {
            throw new ArgumentException("Notification configuration cannot be empty.", nameof(rule.ConfigurationJson));
        }

        if (rule.EnableWebhook)
        {
            if (string.IsNullOrWhiteSpace(rule.WebhookUrl))
            {
                throw new ArgumentException("Webhook url cannot be empty when webhook is enabled.", nameof(rule.WebhookUrl));
            }

            if (!Uri.TryCreate(rule.WebhookUrl, UriKind.Absolute, out _))
            {
                throw new ArgumentException("Webhook url format is invalid.", nameof(rule.WebhookUrl));
            }
        }

        if (!string.IsNullOrWhiteSpace(rule.WebhookSecret) && rule.WebhookSecret.Length > 255)
        {
            throw new ArgumentException("Webhook secret exceeds the maximum length of 255 characters.", nameof(rule.WebhookSecret));
        }

        if (rule.MaxRetryTimes.HasValue && (rule.MaxRetryTimes < 1 || rule.MaxRetryTimes > MaxSupportedRetryTimes))
        {
            throw new ArgumentException($"MaxRetryTimes must be between 1 and {MaxSupportedRetryTimes}.", nameof(rule.MaxRetryTimes));
        }
    }

    private static void ApplyNotificationRuleValues(NotificationRule entity, NotificationRuleRequest request)
    {
        entity.EnableWebhook = request.EnableWebhook;
        entity.WebhookUrl = request.EnableWebhook ? NormalizeWebhookUrl(request.WebhookUrl) : string.Empty;
        entity.WebhookSecret = request.EnableWebhook ? NormalizeSecret(request.WebhookSecret) : string.Empty;
        entity.MaxRetryTimes = NormalizeRetryTimes(request.MaxRetryTimes);
    }

    private static string NormalizeWebhookUrl(string? url) => string.IsNullOrWhiteSpace(url) ? string.Empty : url.Trim();
    private static string NormalizeSecret(string? secret) => string.IsNullOrWhiteSpace(secret) ? string.Empty : secret.Trim();

    private static int NormalizeRetryTimes(int? value)
    {
        if (!value.HasValue || value <= 0)
        {
            return DefaultMaxRetryTimes;
        }

        return Math.Min(value.Value, MaxSupportedRetryTimes);
    }
}
